﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using log4net;
using System.Threading;

namespace Pink.Scheduler.Shared
{
	/// <summary>
	/// Класс, выполняющий загрузку сборки и синхронный запуск задач в текущем домене.
	/// </summary>
	public class SimpleTaskExecutor : MarshalByRefObject, ITaskExecutor
	{
		private ILog logger = LogManager.GetLogger(typeof(SimpleTaskExecutor));
		private Dictionary<string, Type> loadedTasks;
		private LoggingParameters logParameters;

		/// <summary>
		/// Конструктор
		/// </summary>
		/// <param name="logParameters">Правила формирования логов для задачиs</param>
		public SimpleTaskExecutor(LoggingParameters logParameters)
		{
			loadedTasks = new Dictionary<string, Type>();
			this.logParameters = logParameters;
		}

		/// <summary>
		/// Выполнить задачу
		/// </summary>
		/// <param name="id">Идентификатор запускаемой задачи</param>
		/// <param name="taskClass">Класс, который надо создать</param>
		/// <param name="taskAssembly">Сборка с задачей</param>
		/// <param name="taskConfig">Настройки задачи</param>
		/// <param name="executionID">Идентификатор запуска</param>
		public void ExecuteTask(string id, string config, int executionID,
				string taskClass, string taskAssembly)
		{
			using (logParameters.ProvideLoggingContext(id, executionID))
			{
				string assemblyPath = Path.GetFullPath(taskAssembly);
				// для ключа используем абсолютный путь, потому что его могут писать по-разному, но он один.
				string taskTypeKey = string.Format("{0}::{1}", assemblyPath, taskClass);
				// ищем тип задачи.
				Type taskType = null;
				Assembly asm = null;
				lock (loadedTasks)
				{
					if (loadedTasks.ContainsKey(taskTypeKey))
					{
						taskType = loadedTasks[taskTypeKey];
						asm = taskType.Assembly;
					}
					else
					{
						logger.DebugFormat("Тип не найден в кеше. Выполняется первичная загрузка по пути {0}", assemblyPath);
						// LoadFrom кэширует сборки в отличие от Load(..)
						try
						{
							asm = Assembly.LoadFrom(assemblyPath);
							if (asm == null)
								throw new ArgumentException(string.Format("Невозможно загрузить сборку: {0}", assemblyPath));
							taskType = asm.GetType(taskClass);
						}
						catch (Exception ex)
						{
							logger.Fatal("Ошибка загрузки задачи", ex);
							throw;
						}
						if (taskType != null)
							loadedTasks.Add(taskTypeKey, taskType);
					}
				}
				logger.DebugFormat("Используется сборка версии {0} по пути {1}", asm.GetName().Version, asm.Location);
				if (taskType == null)
					throw new ArgumentException(string.Format("Тип обработчика '{0}' не найден", taskClass));
				ITask itask = (ITask)Activator.CreateInstance(taskType);
				logger.DebugFormat("Запуск задачи. Имя исполняющего сервера: {0}", Environment.MachineName);
				try
				{
					itask.Execute(id, config, executionID);
				}
				catch (ThreadAbortException)
				{
					logger.FatalFormat("Принудительное завершение рабочего потока задачи.");
				}
				catch (Exception ex)
				{
					logger.Fatal("Ошибка выполнения задачи.", ex);
				}
			}
		}

		public override object InitializeLifetimeService()
		{
			return null;
		}
	}
}
